#region  == Copyright == 
// =====================================================================
//  Microsoft Consulting Services France - Aymeric Mouillé - 2013
//  Projet     : Bulk Security Role Manager - Bulk Security Role Manager
//  Fichier    : MainForm.cs (25/04/2013 15:02)
//  
//  Copyright (C) Microsoft Corporation.  All rights reserved.
// =====================================================================
#endregion

namespace CRM.BulkSecurityRoleManager
{
    #region  == Using == 
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.IO;
    using System.Linq;
    using System.Windows.Forms;

    using BulkSecurityRoleManager.Properties;

    using Microsoft.Xrm.Client;
    using Microsoft.Xrm.Client.Services;
    using Microsoft.Xrm.Sdk;
    using Microsoft.Xrm.Sdk.Messages;
    using Microsoft.Xrm.Sdk.Query;

    #endregion

    public partial class MainForm : Form
    {
        private IOrganizationService OrganizationService;

        private readonly BackgroundWorker BgWorkerSecurityRoleJob;

        private readonly BackgroundWorker BgWorkerUserJob;

        private readonly BackgroundWorker BgWorkerAssignationJob;

        private List<Entity> Records;

        private string SystemUsersCacheFilePath = "SystemUsersCache.xml";

        private string SecurityRolesCacheFilePath = "SecurityRolesCache.xml";

        private List<Role> Roles;

        private List<User> Users;

        private List<UserRoleAssignation> UserRoleAssignations;

        private string UserAssignationFilePath;

        private delegate void UpdateStatusDelegate(string value, int progress);

        #region = Constructor =
        /// <summary>
        /// Initializes a new instance of the <see cref="MainForm" /> class.
        /// </summary>
        public MainForm()
        {
            InitializeComponent();

            BgWorkerSecurityRoleJob = new BackgroundWorker();
            BgWorkerUserJob = new BackgroundWorker();
            BgWorkerAssignationJob = new BackgroundWorker();

            txtOrgUrl.Text = Settings.Default.OrgUrl;
            txtDomain.Text = Settings.Default.Domain;
            txtLogin.Text = Settings.Default.Login;
            txtPassword.Text = Settings.Default.Password;
        }
        #endregion

        #region Get Crm Connection String
        /// <summary>
        /// Gets the CRM connection string.
        /// </summary>
        /// <returns></returns>
        private string GetCrmConnectionString()
        {
            if (string.IsNullOrEmpty(txtOrgUrl.Text))
            {
                MessageBox.Show(Resources.MainForm_GetCrmConnectionString_You_need_do_specify__organization_url__in_settings_tab);
                return string.Empty;
            }

            if (string.IsNullOrEmpty(txtDomain.Text))
            {
                MessageBox.Show(Resources.MainForm_GetCrmConnectionString_You_need_do_specify__domain__in_settings_tab);
                return string.Empty;
            }

            if (string.IsNullOrEmpty(txtLogin.Text))
            {
                MessageBox.Show(Resources.MainForm_GetCrmConnectionString_You_need_do_specify__login__in_settings_tab);
                return string.Empty;
            }

            if (string.IsNullOrEmpty(txtPassword.Text))
            {
                MessageBox.Show(Resources.MainForm_GetCrmConnectionString_You_need_do_specify__password__in_settings_tab);
                return string.Empty;
            }

            string crmConnectionString = string.Format("Url={0}; Domain={1}; Username={2}; Password={3}", txtOrgUrl.Text, txtDomain.Text, txtLogin.Text, txtPassword.Text);
            return crmConnectionString;
        }
        #endregion

        #region Btn Go Click
        /// <summary>
        /// Handles the Click event of the btnGo control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">
        /// The <see cref="EventArgs" /> instance containing the event data.
        /// </param>
        private void btnGo_Click(object sender, EventArgs e)
        {
            string crmConnectionString = GetCrmConnectionString();
            if (string.IsNullOrEmpty(crmConnectionString))
            {
                return;
            }

            UpdateStripStatus("Connecting ...");
            var connection = CrmConnection.Parse(crmConnectionString);
            connection.Timeout = new TimeSpan(0, 5, 0);
            OrganizationService = new OrganizationService(connection);

            UserAssignationFilePath = txtUserCsvFilePath.Text;
            if (!File.Exists(UserAssignationFilePath))
            {
                MessageBox.Show(string.Format("Path not found : {0}!", UserAssignationFilePath), "Unexpected error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }

            if (!BgWorkerSecurityRoleJob.IsBusy)
            {
                UpdateStripStatus("Loading security roles, please wait ...");
                BgWorkerSecurityRoleJob.DoWork += LoadSecurityRolesWork;
                BgWorkerSecurityRoleJob.RunWorkerCompleted += LoadingSecurityRolesCompleted;
                BgWorkerSecurityRoleJob.RunWorkerAsync();
            }
        }
        #endregion

        #region Load Security Roles Work
        /// <summary>
        /// Loads the security roles work.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">
        /// The <see cref="DoWorkEventArgs" /> instance containing the event data.
        /// </param>
        private void LoadSecurityRolesWork(object sender, DoWorkEventArgs e)
        {
            RetrieveRecordsFromCrm("role");
            Roles = new List<Role>();
            foreach (var roleRecord in Records)
            {
                Role role = new Role();
                role.RoleId = roleRecord.Id;
                role.RoleName = roleRecord.Attributes["name"].ToString();
                if (Roles.All(r => r.RoleName != role.RoleName))
                {
                    Roles.Add(role);
                }
            }
        }
        #endregion

        #region Loading Security Roles Completed
        /// <summary>
        /// Loadings the security roles completed.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">
        /// The <see cref="RunWorkerCompletedEventArgs" /> instance containing the event data.
        /// </param>
        private void LoadingSecurityRolesCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            UpdateStripStatus("Loading system users, please wait ...");

            if (!BgWorkerUserJob.IsBusy)
            {
                BgWorkerUserJob.DoWork += LoadSystemUsersWork;
                BgWorkerUserJob.RunWorkerCompleted += LoadSystemUsersWorkCompleted;
                BgWorkerUserJob.RunWorkerAsync();
            }
        }
        #endregion

        #region Load System Users Work
        /// <summary>
        /// Loads the system users work.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">
        /// The <see cref="DoWorkEventArgs" /> instance containing the event data.
        /// </param>
        private void LoadSystemUsersWork(object sender, DoWorkEventArgs e)
        {
            RetrieveRecordsFromCrm("systemuser");
            Users = new List<User>();
            foreach (var userRecord in Records)
            {
                User user = new User();
                user.UserId = userRecord.Id;
                user.UserName = userRecord.Attributes["fullname"].ToString();
                user.UserLogin = userRecord.Attributes["domainname"].ToString();
                Users.Add(user);
            }
        }
        #endregion

        #region Load System Users Work Completed
        /// <summary>
        /// Loads the system users work completed.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">
        /// The <see cref="RunWorkerCompletedEventArgs" /> instance containing the event data.
        /// </param>
        private void LoadSystemUsersWorkCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            UpdateStripStatus("Loading assignations, please wait ...");
            if (!BgWorkerUserJob.IsBusy)
            {
                UserRoleAssignations = new List<UserRoleAssignation>();
                var fileLines = File.ReadLines(UserAssignationFilePath);
                int i = 0;
                foreach (var fileLine in fileLines)
                {
                    if (i == 0)
                    {
                        i++;
                        continue;
                    }
                    var lineTab = fileLine.Split(';');

                    User user = Users.FirstOrDefault(u => u.UserLogin == lineTab[0]);
                    if (user == null)
                    {
                        throw new Exception(string.Format("User not found : {0}", lineTab[0]));
                    }
                    Role role = Roles.FirstOrDefault(r => r.RoleName == lineTab[1]);
                    if (role == null)
                    {
                        throw new Exception(string.Format("Role not found : {0}", lineTab[1]));
                    }
                    UserRoleAssignations.Add(new UserRoleAssignation(user, role));
                    i++;
                }
                dgvUserAssignations.DataSource = UserRoleAssignations;
                dgvUserAssignations.Columns[4].Width = 500;
                tabControl.SelectTab(1);

                UpdateStripStatus("Apply user/role assignations, please wait ...");
                BgWorkerAssignationJob.DoWork += ApplyAssignationsWork;
                BgWorkerAssignationJob.RunWorkerCompleted += ApplyAssignationsCompleted;
                BgWorkerAssignationJob.ProgressChanged += ApplyAssignationsProgressChanged;
                BgWorkerAssignationJob.WorkerReportsProgress = true;
                BgWorkerAssignationJob.RunWorkerAsync();
            }
        }
        #endregion

        #region Apply Assignations Work
        /// <summary>
        /// Applies the assignations work.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">
        /// The <see cref="DoWorkEventArgs" /> instance containing the event data.
        /// </param>
        private void ApplyAssignationsWork(object sender, DoWorkEventArgs e)
        {
            int i = 0;
            foreach (var userRoleAssignation in UserRoleAssignations)
            {
                try
                {
                    AssociateRequest request = new AssociateRequest();
                    request.RelatedEntities = new EntityReferenceCollection();
                    request.RelatedEntities.Add(new EntityReference("systemuser", userRoleAssignation.UserId));
                    request.Relationship = new Relationship("systemuserroles_association");

                    request.Target = new EntityReference("role", userRoleAssignation.RoleId);

                    OrganizationService.Execute(request);
                    userRoleAssignation.Status = "OK";
                }
                catch (Exception ex)
                {
                    userRoleAssignation.Status = "ERROR : " + ex.Message;
                }
                finally
                {
                    i++;
                    int percent = i * 100 / UserRoleAssignations.Count;
                    UpdateStripStatus("Apply user/role assignations, please wait ...", percent);
                    BgWorkerAssignationJob.ReportProgress(percent);
                }
            }
        }
        #endregion

        #region Apply Assignations Progress Changed
        /// <summary>
        /// Applies the assignations progress changed.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">
        /// The <see cref="ProgressChangedEventArgs" /> instance containing the event data.
        /// </param>
        private void ApplyAssignationsProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            dgvUserAssignations.DataSource = null;
            dgvUserAssignations.DataSource = UserRoleAssignations;

            dgvUserAssignations.Columns[4].Width = 500;
            dgvUserAssignations.Refresh();
        }
        #endregion

        #region Apply Assignations Completed
        /// <summary>
        /// Applies the assignations completed.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">
        /// The <see cref="RunWorkerCompletedEventArgs" /> instance containing the event data.
        /// </param>
        private void ApplyAssignationsCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            UpdateStripStatus("Operation completed", 0);
            MessageBox.Show("Operation completed", "Information", MessageBoxButtons.OK, MessageBoxIcon.Information);
        }
        #endregion

        #region Retrieve Records From Crm
        /// <summary>
        /// Retrieves the records from CRM.
        /// </summary>
        /// <param name="entityLogicalName">Name of the entity logical.</param>
        public void RetrieveRecordsFromCrm(string entityLogicalName)
        {
            Records = new List<Entity>();
            string cacheFilePath = string.Empty;
            if (entityLogicalName == "role")
            {
                cacheFilePath = SecurityRolesCacheFilePath;
            }
            else if (entityLogicalName == "systemuser")
            {
                cacheFilePath = SystemUsersCacheFilePath;
            }
            if (File.Exists(cacheFilePath))
            {
                Records = SerializationHelper.Deserialize(cacheFilePath);
            }
            else
            {
                var query = new QueryExpression(entityLogicalName) { PageInfo = new PagingInfo { Count = 250, PageNumber = 1 }, ColumnSet = new ColumnSet(true) };

                var doContinue = true;
                while (doContinue)
                {
                    var recordCollection = OrganizationService.RetrieveMultiple(query);
                    foreach (var record in recordCollection.Entities)
                    {
                        Records.Add(record);
                    }

                    doContinue = recordCollection.MoreRecords;
                    query.PageInfo.PageNumber += 1;
                }
                SerializationHelper.Serialize(Records, typeof (List<Entity>), cacheFilePath);
            }
        }
        #endregion

        #region Can Try Again
        /// <summary>
        /// Determines whether this instance [can try again] the specified can try again.
        /// </summary>
        /// <param name="canTryAgain">
        /// if set to <c>true</c> [can try again].
        /// </param>
        private void CanTryAgain(bool canTryAgain)
        {
            toolStripSplitButton.Visible = canTryAgain;
        }
        #endregion

        #region Tool Strip Split Button Button Click
        /// <summary>
        /// Handles the ButtonClick event of the toolStripSplitButton control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">
        /// The <see cref="EventArgs" /> instance containing the event data.
        /// </param>
        private void toolStripSplitButton_ButtonClick(object sender, EventArgs e)
        {
            CanTryAgain(false);
        }
        #endregion

        #region Update Strip Status
        /// <summary>
        /// Updates the strip status.
        /// </summary>
        /// <param name="message">The message.</param>
        /// <param name="progress">The progress.</param>
        private void UpdateStripStatus(string message, int progress = -1)
        {
            if (InvokeRequired)
            {
                // We're not in the UI thread, so we need to call BeginInvoke
                BeginInvoke(new UpdateStatusDelegate(UpdateStripStatus), new object[] { message, progress });
                return;
            }

            // Must be on the UI thread if we've got this far
            toolStripStatusLabel.Text = message;
            if (progress == -1)
            {
                toolStripProgressBar.Style = ProgressBarStyle.Marquee;
            }
            else
            {
                toolStripProgressBar.Style = ProgressBarStyle.Blocks;
                toolStripProgressBar.Value = progress;
            }
            Application.DoEvents();
        }
        #endregion

        #region Txt Org Url Text Changed
        /// <summary>
        /// Handles the TextChanged event of the txtOrgUrl control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">
        /// The <see cref="EventArgs" /> instance containing the event data.
        /// </param>
        private void txtOrgUrl_TextChanged(object sender, EventArgs e)
        {
            Settings.Default.OrgUrl = txtOrgUrl.Text;
            Settings.Default.Save();
        }
        #endregion

        #region Txt Domain Text Changed
        /// <summary>
        /// Handles the TextChanged event of the txtDomain control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">
        /// The <see cref="EventArgs" /> instance containing the event data.
        /// </param>
        private void txtDomain_TextChanged(object sender, EventArgs e)
        {
            Settings.Default.Domain = txtDomain.Text;
            Settings.Default.Save();
        }
        #endregion

        #region Txt Login Text Changed
        /// <summary>
        /// Handles the TextChanged event of the txtLogin control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">
        /// The <see cref="EventArgs" /> instance containing the event data.
        /// </param>
        private void txtLogin_TextChanged(object sender, EventArgs e)
        {
            Settings.Default.Login = txtLogin.Text;
            Settings.Default.Save();
        }
        #endregion

        #region Txt Password Text Changed
        /// <summary>
        /// Handles the TextChanged event of the txtPassword control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">
        /// The <see cref="EventArgs" /> instance containing the event data.
        /// </param>
        private void txtPassword_TextChanged(object sender, EventArgs e)
        {
            Settings.Default.Password = txtPassword.Text;
            Settings.Default.Save();
        }
        #endregion

        #region Btn Browse Click
        /// <summary>
        /// Handles the Click event of the btnBrowse control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">
        /// The <see cref="EventArgs" /> instance containing the event data.
        /// </param>
        private void btnBrowse_Click(object sender, EventArgs e)
        {
            if (openFileDialog.ShowDialog() == DialogResult.OK)
            {
                txtUserCsvFilePath.Text = openFileDialog.FileName;
            }
        }
        #endregion

        #region Btn Clear Cache Click
        /// <summary>
        /// Handles the Click event of the btnClearCache control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">
        /// The <see cref="EventArgs" /> instance containing the event data.
        /// </param>
        private void btnClearCache_Click(object sender, EventArgs e)
        {
            if (File.Exists(SystemUsersCacheFilePath))
            {
                File.Delete(SystemUsersCacheFilePath);
            }
            if (File.Exists(SecurityRolesCacheFilePath))
            {
                File.Delete(SecurityRolesCacheFilePath);
            }
        }
        #endregion
    }
}
